/**
* This component handles rendering a tree view in a data grid
*/

<template>
  <div class="v-tree-data-table">
    <v-data-table
      ref="dataTable"
      v-model="selected"
      :show-select="selectAll"
      :single-select="singleSelect"
      :dense="dense"
      :headers="computedHeaders"
      :items="flattenedNodes"
      :options.sync="paginationOptions"
      :server-items-length="totalItems"
      :loading="loading"
      :loading-text="loadingText"
      :footer-props="footerProps"
      :mobile-breakpoint="mobileBreakpoint"
    >
      <template v-slot:no-data>
        <tr>
          <td
            :colspan="headers.length + (selectAll ? 1 : 0)"
            class="text-xs-center"
          >
            没有找到匹配的记录
          </td>
        </tr>
      </template>

      <template v-slot:[`header.data-table-select`]="{ props }">
        <thead>
          <tr>
            <th v-if="selectAll" style="padding-left: 0px; width: 50px">
              <v-checkbox
                :input-value="props.all"
                :indeterminate="props.indeterminate"
                primary
                hide-details
                @click.native="toggleSelectAll"
              ></v-checkbox>
            </th>
          </tr>
        </thead>
      </template>

      <template slot="item" slot-scope="props">
        <tr
          v-if="props.index == 0"
          class="drop-row inactive"
          :style="nodeHidden(props.item)"
          @dragenter.stop.prevent="dragEnterSlot($event)"
          @dragleave.stop.prevent="dragLeaveSlot($event)"
          @drop.stop.prevent="dropRow($event)"
          @dragover.stop="dragOverSlot($event)"
        >
          <td :colspan="computedHeaders.length + (selectAll ? 1 : 0)"></td>
        </tr>

        <tr
          v-if="props.item.leaf"
          class="leaf"
          :active="props.isSelected"
          @dblclick="
            (e) => {
              $emit('dblclick', e, props.item);
            }
          "
          @contextmenu.prevent="
            (e) => {
              $emit('contextmenu', e, props.item);
            }
          "
          :key="props.item.id"
          :id="props.item.id"
          :style="nodeHidden(props.item)"
          @dragenter.stop.prevent="dragEnterLeaf($event)"
          @dragleave.stop.prevent="dragLeaveLeaf($event)"
          @drop.stop.prevent="dropRow($event)"
          @dragover.stop="dragOverLeaf($event)"
        >
          <td v-if="selectAll">
            <v-checkbox
              @click="selectedItem(props.item, props.isSelected)"
              :input-value="props.isSelected"
              primary
              hide-details
            ></v-checkbox>
          </td>
          <td
            v-if="props.item.leaf"
            class="expandable-node"
            :style="nodeStyle(props.item)"
          >
            <v-icon>mdi-chevron-right</v-icon>
          </td>
          <td
            v-else
            class="expandable-node"
            @click="toggleNode(props.item)"
            :style="nodeStyle(props.item)"
          >
            <v-icon>{{ expandableIcon(props.item) }}</v-icon>
          </td>
          <slot name="row" v-bind="props"></slot>
        </tr>

        <tr
          v-if="!props.item.leaf"
          class="folder"
          :active="props.isSelected"
          @dblclick="
            (e) => {
              $emit('dblclick', e, props.item);
            }
          "
          @contextmenu.prevent="
            (e) => {
              $emit('contextmenu', e, props.item);
            }
          "
          :key="props.item.id"
          :id="props.item.id"
          :style="nodeHidden(props.item)"
          @dragenter.stop.prevent="dragEnterFolder($event)"
          @dragleave.stop.prevent="dragLeaveFolder($event)"
          @drop.stop.prevent="dropRow($event)"
          @dragover.stop="dragOverFolder($event)"
        >
          <td v-if="selectAll">
            <v-checkbox
              @click="selectedItem(props.item, props.isSelected)"
              :input-value="props.isSelected"
              primary
              hide-details
            ></v-checkbox>
          </td>
          <td
            v-if="props.item.leaf"
            class="expandable-node"
            :style="nodeStyle(props.item)"
          >
            <v-icon>mdi-chevron-right</v-icon>
          </td>
          <td
            v-else
            class="expandable-node"
            @click="toggleNode(props.item)"
            :style="nodeStyle(props.item)"
          >
            <v-icon>{{ expandableIcon(props.item) }}</v-icon>
          </td>
          <slot name="row" v-bind="props"></slot>
        </tr>

        <tr
          class="drop-row inactive"
          :style="nodeHidden(props.item)"
          @dragenter.stop.prevent="dragEnterSlot($event)"
          @dragleave.stop.prevent="dragLeaveSlot($event)"
          @drop.stop.prevent="dropRow($event)"
          @dragover.stop="dragOverSlot($event)"
        >
          <td :colspan="computedHeaders.length + (selectAll ? 1 : 0)"></td>
        </tr>
      </template>
    </v-data-table>
  </div>
</template>

<script>
import nodeHelper from "./nodeHelper.js";

export default {
  props: {
    headers: {
      type: Array,
      default: [],
      required: true,
    },
    items: {
      type: Array,
      default: [],
      required: true,
    },
    pagination: {
      type: Object,
      required: true,
    },
    totalItems: {
      type: Number,
      required: true,
    },
    loading: {
      type: Boolean,
      required: true,
    },
    selectAll: {
      type: [Boolean, String],
      default: undefined,
    },
    value: {
      type: Array,
      default: undefined,
    },
    validDrop: {
      type: Function,
      default: undefined,
    },
  },

  mounted: function () {
    this.setupDragGhosts();
  },

  data: function () {
    return {
      dense: true, //是否紧凑（降低行高）
      loadingText: "数据加载中...请稍候！", //加载条内容
      singleSelect: false,
      footerProps: {
        "items-per-page-options": [200, 100, 50, 20], //自定义每页行数的下拉框  -1 代表全部
        "items-per-page-text": "每页行数",
        // "items-per-page-all-text": "全部",
      },
      mobileBreakpoint: 50, //当手机屏幕尺寸<= 50px 时，按照手机的样式进行渲染（这里设置小一些就可以实现手机适配的效果了）
      paginationOptions: {},
      flattenedNodes: [],
      overFolder: null,
      draggedNode: null,
      newParentNode: null,
      selected: [],
    };
  },

  watch: {
    paginationOptions() {
      this.$emit("load", this.paginationOptions);
    },
    items() {
      this.flattenNodes();
    },
    value() {
      this.selected = this.value;
    },
    selected() {
      this.$emit("input", this.selected);
    },
  },

  computed: {
    computedHeaders() {
      let headers = this.headers;
      headers.unshift({
        text: " ",
        value: "sortable",
        sortable: false,
      });
      return headers;
    },
  },

  methods: {
    /**
     * 父组件获取选中项
     */
    getSelectedTableItems() {
      return this.selected;
    },
    /**
     * 当选择全部被切换时触发
     */
    toggleSelectAll() {
      if (this.selected.length > 0) {
        this.selected = [];
      } else {
        this.selected = this.items;
      }
    },
    /**
     * 当选择行的复选框时触发
     */
    selectedItem(item, isSelected) {
      if (isSelected) {
        if (this.selected.indexOf(item) > -1) {
          this.$common.arrayRemoveByValue(this.selected, item);
        }
      } else {
        if (this.selected.indexOf(item) == -1) {
          this.selected.push(item);
        }
      }
    },
    /*
    设置拖动鬼图像
    */
    setupDragGhosts: function () {
      this.folderClosedDragGhost = new Image();
      this.folderClosedDragGhost.src =
        "";
      this.folderClosedDragGhost.height = "24";
      this.folderClosedDragGhost.width = "24";

      this.folderOpenDragGhost = new Image();
      this.folderOpenDragGhost.src =
        "";
      this.folderOpenDragGhost.height = "24";
      this.folderOpenDragGhost.width = "24";

      this.leafDragGhost = new Image();
      this.leafDragGhost.src =
        "";
      this.leafDragGhost.height = "24";
      this.leafDragGhost.width = "24";
    },

    /**
     * dragStart handler
     * @param {Event} event Browser Event
     */
    dragStart(event) {
      const draggedNodeId = $(event.target).closest("tr").attr("id");

      this.draggedNode = this.flattenedNodes.filter((_node) => {
        return _node.id == draggedNodeId;
      })[0];

      if (this.draggedNode.leaf) {
        event.dataTransfer.setDragImage(this.leafDragGhost, -24, -24);
      } else if (this.draggedNode.expanded) {
        event.dataTransfer.setDragImage(this.folderOpenDragGhost, -24, -24);
      } else {
        event.dataTransfer.setDragImage(this.folderClosedDragGhost, -24, -24);
      }
    },

    /**
     * dragEnd handler, clears all active rows
     */
    dragEnd() {
      this.clearActive();
    },

    /*
     * Slots
     */

    /**
     * dragEnter handler for a slot
     * @param {Event} event Browser Event
     */
    dragEnterSlot(event) {
      const vm = this;
      const $target = $(event.target);

      this.clearActive();

      if (
        ($target.parents(".drop-row").length > 0 ||
          $target.hasClass(".drop-row")) &&
        this.draggedNode
      ) {
        let validDrop = true;

        if (this.draggedNode && vm.validDrop) {
          validDrop = vm.validDrop(
            this.draggedNode,
            $target.parents(".drop-row"),
            event
          );
        }

        if (
          validDrop &&
          parseInt($target.parents(".drop-row").prev().attr("id")) !=
            this.draggedNode.id &&
          parseInt($target.parents(".drop-row").next().attr("id")) !=
            this.draggedNode.id
        ) {
          $target.parents(".drop-row").removeClass("inactive");
          $target.parents(".drop-row").addClass("active");
        }
      }
    },

    /**
     * dragLeave event handler for a slot
     * @param {Event} event Browser Event
     */
    dragLeaveSlot(event) {
      $(event.target).parents(".drop-row").removeClass("active");
      $(event.target).parents(".drop-row").addClass("inactive");
    },

    /**
     * dragOver event handler for a slot
     * @param {Event} event Browser Event
     */
    dragOverSlot(event) {
      const vm = this;
      const $target = $(event.target);
      let validDrop = true;

      if (this.draggedNode && vm.validDrop) {
        validDrop = vm.validDrop(
          this.draggedNode,
          $target.parents(".drop-row"),
          event
        );
      }

      if (
        validDrop &&
        this.draggedNode &&
        parseInt($target.parents(".drop-row").prev().attr("id")) !=
          this.draggedNode.id &&
        parseInt($target.parents(".drop-row").next().attr("id")) !=
          this.draggedNode.id
      ) {
        event.preventDefault();
      }
    },

    /*
     * Folders
     */

    /**
     * dragEnter event handler for a folder
     * @param {Event} event Browser Event
     */
    dragEnterFolder(event) {
      const vm = this;
      const $target = $(event.target);

      this.clearActive();

      this.overFolder = $target.parents(".folder")[0];

      let validDrop = true;

      if (this.draggedNode && vm.validDrop) {
        validDrop = vm.validDrop(
          this.draggedNode,
          $target.parents(".folder"),
          event
        );
      }

      if (this.overFolder) {
        if (this.draggedNode && this.draggedNode.parentNode) {
          if (
            validDrop &&
            $target.parents(".folder").attr("id") !=
              this.draggedNode.parentNode.id &&
            $target.parents(".folder").attr("id") != this.draggedNode.id
          ) {
            $target.parents(".folder").addClass("active");
          }
        } else if (
          validDrop &&
          $target.parents(".folder").attr("id") != this.draggedNode.id
        ) {
          $target.parents(".folder").addClass("active");
        }
      }
    },

    /**
     * dragLeave event handler for a folder
     * @param {Event} event Browser Event
     */
    dragLeaveFolder(event) {
      const $target = $(event.relatedTarget);

      if (this.overFolder) {
        if (!$.contains(this.overFolder, $target[0])) {
          $(this.overFolder).removeClass("active");
          this.overFolder = null;
        }
      }
    },

    /**
     * dragOver event handler for a folder
     * @param {Event} event Browser Event
     */
    dragOverFolder(event) {
      const vm = this;
      const $target = $(event.target);
      let validDrop = true;

      if (this.draggedNode && vm.validDrop) {
        validDrop = vm.validDrop(
          this.draggedNode,
          $target.parents(".folder"),
          event
        );
      }

      if (this.draggedNode) {
        if (
          validDrop &&
          this.draggedNode.parentNode &&
          $target.parents(".folder").attr("id") !=
            this.draggedNode.parentNode.id &&
          $target.parents(".folder").attr("id") != this.draggedNode.id
        ) {
          event.preventDefault();
        } else if (
          validDrop &&
          $target.parents(".folder").attr("id") != this.draggedNode.id
        ) {
          event.preventDefault();
        }
      }
    },

    /*
     * Leaves
     */

    /**
     * dragEnter event handler for a leaf
     * @param {Event} event Browser Event
     */
    dragEnterLeaf(event) {
      const vm = this;
      const $target = $(event.target);
      let validDrop = true;

      if ((this.draggedNode, vm.validDrop)) {
        validDrop = vm.validDrop(
          this.draggedNode,
          $target.parents(".leaf"),
          event
        );
      }

      this.clearActive();

      this.overFolder = $target.parents(".leaf")[0];

      if (this.draggedNode) {
        if (
          validDrop &&
          this.draggedNode.parentNode &&
          $target.parents(".leaf").attr("id") !=
            this.draggedNode.parentNode.id &&
          $target.parents(".leaf").attr("id") != this.draggedNode.id
        ) {
          $target.parents(".leaf").addClass("active");
        } else if (
          validDrop &&
          $target.parents(".leaf").attr("id") != this.draggedNode.id
        ) {
          $target.parents(".leaf").addClass("active");
        }
      }
    },

    /**
     * dragLeave event handler for a leaf
     * @param {Event} event Browser Event
     */
    dragLeaveLeaf(event) {
      const $target = $(event.relatedTarget);

      if (this.overFolder) {
        if (!$.contains(this.overFolder, $target[0])) {
          $(this.overFolder).removeClass("active");
          this.overFolder = null;
        }
      }
    },

    /**
     * dragOver event handler for a leaf
     * @param {Event} event Browser Event
     */
    dragOverLeaf(event) {
      const vm = this;
      const $target = $(event.target);
      let validDrop = true;

      if ((this.draggedNode, vm.validDrop)) {
        validDrop = vm.validDrop(
          this.draggedNode,
          $target.parents(".leaf"),
          event
        );
      }

      if (this.draggedNode) {
        if (this.draggedNode.parentNode) {
          if (
            validDrop &&
            $target.parents(".leaf").attr("id") !=
              this.draggedNode.parentNode.id &&
            $target.parents(".leaf").attr("id") != this.draggedNode.id
          ) {
            event.preventDefault();
          }
        } else if (
          validDrop &&
          $target.parents(".leaf").attr("id") != this.draggedNode.id
        ) {
          event.preventDefault();
        }
      }
    },

    /**
     * drop event handler
     * @param {Event} event Browser Event
     */
    dropRow(event) {
      const vm = this;
      const $target = $(event.target);

      if ($target.parents(".folder").length > 0) {
        const parentNodeId = $(event.target).parents(".folder").attr("id");

        this.newParentNode = this.flattenedNodes.filter((_node) => {
          return _node.id == parentNodeId;
        })[0];

        $(this.overFolder).removeClass("active");
      } else if ($target.parents(".leaf").length > 0) {
        const parentNodeId = $(event.target).parents(".leaf").attr("id");

        this.newParentNode = this.flattenedNodes.filter((_node) => {
          return _node.id == parentNodeId;
        })[0];

        $(this.overFolder).removeClass("active");

        this.collapseChildren(this.draggedNode);
      } else {
        const previousSiblingId = $(event.target)
          .parents(".drop-row")
          .prev()
          .attr("id");
        const nextSiblingId = $(event.target)
          .parents(".drop-row")
          .next()
          .attr("id");

        this.previousSibling = this.flattenedNodes.filter((_node) => {
          return _node.id == previousSiblingId;
        })[0];
        this.nextSibling = this.flattenedNodes.filter((_node) => {
          return _node.id == nextSiblingId;
        })[0];

        // remove drag classes
        $(event.target).parents(".drop-row").removeClass("active");
        $(event.target).parents(".drop-row").addClass("inactive");
      }

      this.processDrop();
    },

    /**
     * Process the drop event, move node if needed
     */
    processDrop() {
      if (this.draggedNode) {
        const oldParent = this.draggedNode.parentNode;

        if (this.newParentNode) {
          nodeHelper.moveNode(this.draggedNode, this.newParentNode);

          this.overFolder = null;
          this.newParentNode = null;
        } else {
          let currentIndex = this.flattenedNodes.indexOf(this.draggedNode);
          let newIndex = 0;

          this.flattenedNodes.splice(currentIndex, 1);

          if (this.previousSibling) {
            newIndex = this.flattenedNodes.indexOf(this.previousSibling) + 1;

            // If the previous sibilings parent is not the same as the dragged nodes parent then
            // we need to move the dragged node to the parent
            if (
              this.nextSibling &&
              this.nextSibling.parentNode &&
              this.draggedNode.parentNode &&
              this.draggedNode.parentNode.id &&
              this.nextSibling.parentNode.id != this.draggedNode.parentNode.id
            ) {
              nodeHelper.moveNode(
                this.draggedNode,
                this.nextSibling.parentNode
              );
            }

            if (
              this.nextSibling &&
              this.draggedNode.position > this.nextSibling.position
            ) {
              this.draggedNode.position = this.nextSibling.position;
            } else if (this.previousSibling) {
              this.draggedNode.position = this.previousSibling.position;
            } else {
              this.draggedNode.position = 0;
            }
          } else {
            if (this.draggedNode.parentNode) {
              this.draggedNode.parentNode.children.splice(
                this.draggedNode.parentNode.children.indexOf(this.draggedNode),
                1
              );
              this.draggedNode.parentNode = null;
              this.draggedNode.depth = 1;
            }

            this.draggedNode.position = 0;
          }

          this.flattenedNodes.splice(newIndex, 0, this.draggedNode);
        }

        const currentNodes = this.unFlattenNodes();

        this.flattenNodes(this.unFlattenNodes());

        this.$emit(
          "drop",
          this.draggedNode,
          oldParent,
          this.draggedNode.parentNode,
          this.previousSibling,
          this.nextSibling,
          this.unFlattenNodes(),
          () => {
            this.flattenNodes(currentNodes);
          }
        );

        this.draggedNode = null;
      }
    },

    /**
     * Find the node in items
     * @param {String | Number} nodeId The Node
     * @return {Object} the Node
     */
    findNode(nodeId) {
      nodeHelper.findNode(nodeId, this.items);
    },

    /**
     * Get the icon for the node
     * @param {Object} node The Node
     */
    expandableIcon(node) {
      if (node.expanded) {
        return "mdi-minus";
      } else {
        return "mdi-plus";
      }
    },

    /**
     * Get the style padding for the node
     * @param {Object} node The Node
     */
    nodeStyle(node) {
      var pLeft = 8 + 12 * (node.depth - 1);
      return {
        paddingLeft: pLeft + "px",
      };
    },

    /**
     * Flatten nested nodes
     */
    flattenNodes(nodes) {
      nodes = nodes || this.items;

      this.flattenedNodes = nodeHelper.flattenNodes(nodes);
    },

    /**
     * UnFlatten flattend nodes
     */
    unFlattenNodes() {
      return nodeHelper.unFlattenNodes(this.flattenedNodes);
    },

    /**
     * Determine if a node should be hidden
     * @param {Object} node The Node
     */
    nodeHidden(node) {
      if (!node.parentNode) {
        return null;
      } else if (node.parentNode.expanded) {
        return null;
      } else {
        return "display:none;";
      }
    },

    /**
     * Toggle node from expanded
     * @param {Object} node The Node
     */
    toggleNode(node) {
      this.$set(node, "expanded", !node.expanded);

      if (!node.expanded) this.collapseChildren(node);

      this.$emit("node-toggle", node);
    },

    /**
     * Collapse all nodes children
     * @param {Object} node The Node
     */
    collapseChildren(node) {
      let vm = this;

      this.$set(node, "expanded", false);

      if (node.children) {
        node.children.forEach((child) => {
          vm.collapseChildren(child);
        });
      }
    },

    /*
     * Helpers
     */

    /**
     * Clear any active classes
     */
    clearActive() {
      $(this.$el).find(".active").removeClass("active");
    },
  },
};
</script>

<style>
.v-tree-data-table .folder {
  cursor: pointer;
}
.v-tree-data-table .drag * {
  pointer-events: none;
}
.v-tree-data-table .inactive {
  border: none !important;
  height: 0px;
  background-color: transparent !important;
}
.v-tree-data-table .inactive td {
  height: 0px !important;
}

.v-tree-data-table .active td {
  height: 15px !important;
}
.v-tree-data-table .sort-handle {
  cursor: move;
}

.v-input--checkbox {
  padding-top: 0px;
  margin-top: 0px;
}

/* 设置扩展列的宽度 */
.expandable-node {
  width: 60px !important;
  min-width: 60px !important;
}
</style>